home *** CD-ROM | disk | FTP | other *** search
/ EuroCD 3 / EuroCD 3.iso / Programming / Python-1.4 / Demo / ChatServer.py next >
Text File  |  1998-06-24  |  12KB  |  394 lines

  1. #
  2. #    Multiuser Chat Server. (6-apr-96) by Irmen de Jong.
  3. #        Extended by Frank Niessink
  4. #
  5. #    Implements a mud-like "chat" server to which multiple users can connect,
  6. #    by simple telnet. Some simple commands are supported.
  7. #    Feelings are supported, but not fully. No adverbs yet.
  8. #
  9. #    Usage: python chatserv.py [hostname] [portnumber]
  10. #
  11.  
  12. import select
  13. import string
  14. import sys
  15. import time
  16. import socket
  17.  
  18. WelcomeMsg = "\r\nTELNET CHAT SERVICE...\r\n"
  19.  
  20. class connection:
  21.     def __init__(self,sock,address,name=None):
  22.         self.sock=sock
  23.         self.myname=name
  24.         self.address=address
  25.  
  26.     def __del__(self):
  27.         self.sock.close()
  28.         self.sock=None
  29.  
  30.     def setname(self,name):
  31.         self.myname=name
  32.  
  33.     def name(self):
  34.         return self.myname
  35.  
  36.     def send(self,msg):
  37.         self.sock.send(msg)
  38.     def senderr(self,errmsg):
  39.         self.sock.send(mkerr(errmsg))
  40.     def sendnote(self,msg):
  41.         self.sock.send(mknote(msg))
  42.  
  43.  
  44. # The following list contain all open connections.
  45. connections=[]
  46.  
  47. #Feeling types:
  48. SIMP=0; DEFA=1; PERS=2; QUAD=3; PREV=4; PHYS=5; DEUX=6; SHRT=7
  49.  
  50. #feelings.. taken from LPC mud soul. Not yet fully supported.
  51. feelings = {
  52. "hmm":        [0,0," hmm$ \nHOW \nAT"," at"],
  53. "burp":        [1,["rudely"],""," at"],
  54. "wink":        [1,["suggestively"],""," at"],
  55. "smile":    [1,["happily"],""," at"],
  56. "point":    [1,0,""," at"],
  57. "grin":        [1,["evilly"],""," at"],
  58. "laugh":    [1,0,""," at"],
  59. "nod":        [1,["solemnly"],""," at"],
  60. "wave":        [1,["happily"],""," at"],
  61. "cackle":    [1,["with glee"],""," at"],
  62. "chuckle":    [1,["politely"],""," at"],
  63. "bow":        [1,0,""," to"],
  64. "glare":    [1,["stonily"],""," at"],
  65. "giggle":    [1,["merrily"],""," at"],
  66. "groan":    [1,0,""," at"],
  67. "grunt":    [1,0,""," at"],
  68. "growl":    [1,0,""," at"],
  69. "snarl":    [1,0,""," at"],
  70. "recoil":    [1,["with fear"],""," from"],
  71. "moan":        [1,0,""," at"],
  72. "howl":        [1,["in pain"],""," at"],
  73. "puke":        [1,0,""," on"],
  74. "sneeze":    [1,["loudly"],""," at"],
  75. "spit":        [1,0,""," on"],
  76. "stare":    [1,0,""," at"],
  77. "whistle":    [1,["appreciatively"],""," at"],
  78. "applaud":    [1,0,""," at"],
  79. "agree":    [1,0,""," with"],
  80. "disagree":    [1,0,""," with"],
  81. "fart":        [1,0,""," at"],
  82. "dance":    [1,0,""," with"],
  83. "purr":        [1,0,""," at"],
  84. "listen":    [1,0,""," to"],
  85. "apologize":    [1,0,""," to"],
  86. "complain":    [1,0,""," about"],
  87. "beg":        [2,0," beg$ \nHOW"," beg$ \nWHO for mercy \nHOW"],
  88. "shake":    [2,0," shake$ \nYOUR head \nHOW"," shake$ hands with \nWHO \nHOW"],
  89. "grimace":    [0,0," \nHOW make$ an awful face \nAT"," at"],
  90. "stomp":    [2,0," stomp$ \nYOUR foot \nHOW"," stomp$ on \nPOSS foot \nHOW"],
  91. "snigger":    [1,["jeeringly"],""," at"],
  92. "watch":    [3,["carefully"]," watch the surroundings \nHOW", " watches the surroundings \nHOW"," watch \nWHO \nHOW"," watches \nWHO \nHOW",],
  93. "scratch":    [3,["on the head"]," scratch \nMYself \nHOW"," scratches \nMYself \nHOW"," scratch \nWHO \nHOW"," scratches \nWHO \nHOW",],
  94. "tap":        [2,["impatiently",0,"on the shoulder"]," tap$ \nYOUR foot \nHOW"," tap$ \nWHO \nWHERE"],
  95. "curse":    [2,0," curse$ \nWHAT \nHOW"," curse$ \nWHO \nHOW"],
  96. "sing":        [0,0," \nHOW sing$ \nWHAT \nAT"," to"],
  97. "mumble":    [0,0," mumble$ \nWHAT \nHOW \nAT"," to"],
  98. "scream":    [0,["loudly"]," scream$ \nWHAT \nAT \nHOW"," at"],
  99. "yell":        [0,["in a high pitched voice"]," yell$ \nHOW \nWHAT \nAT"," at"],
  100. "hide":        [0,0," hide$ \nHOW behind \nWHO"],
  101. "pounce":    [5,["playfully"],""],
  102. "kneel":    [0,0," \nHOW fall$ on \nYOUR knees \nAT"," in front of"],
  103. "roll":        [0,["to the ceiling"]," roll$ \nYOUR eyes \nHOW"],
  104. "boggle":    [0,0," boggle$ \nHOW at the concept"],
  105. "cheer":    [7,["enthusiastically"],""],
  106. "wiggle":    [0,0," wiggle$ \nYOUR bottom \nHOW"],
  107. "flip":        [0,0," flip$ \nHOW head over heels"],
  108. "cry":        [6,0," cry \nHOW"," cries \nHOW"],
  109. "sob":        [7,0,""],
  110. "sweat":    [7,0,""],
  111. "gurgle":    [7,0,""],
  112. "grumble":    [7,0,""],
  113. "die":        [6,0," fall \nHOW down and play dead"," falls \nHOW to the ground, dead"],
  114. "stumble":    [7,0,""],
  115. "bounce":    [7,0,""],
  116. "yawn":        [7,0,""],
  117. "sulk":        [7,["in the corner"],""],
  118. "strut":    [7,["proudly"],""],
  119. "sniff":    [7,0,""],
  120. "snore":    [7,0,""],
  121. "snicker":    [7,0,""],
  122. "smirk":    [7,0,""],
  123. "jump":        [0,["in aggravation"] ," jump$ up and down \nHOW"],
  124. "fume":        [7,0 ,""],
  125. "faint":    [7,0,""],
  126. "shrug":    [7,0,""],
  127. "pout":        [7,0,""],
  128. "hiccup":    [7,0,""],
  129. "frown":    [7,0,""],
  130. "gasp":        [7,["in astonishment"],""],
  131. "think":    [7,["carefully"],""],
  132. "ponder":    [7,["over some problem"],""],
  133. "clap":        [7,0,""],
  134. "sigh":        [7,0,""],
  135. "cough":    [7,["noisily"],""],
  136. "shiver":    [7,["from the cold"],""],
  137. "blush":    [6,0," blush \nHOW"," blushes \nHOW"]
  138. }
  139.  
  140. # exit : graceful exit
  141. def exit(code):
  142.     global connections
  143.     del connections
  144.     raise SystemExit,code
  145.  
  146. # stripcrlf: strip CR/LF or LF from string
  147. def stripcrlf(str):
  148.     if len(str)>2:
  149.         if (str[-2:]=='\r\n') or (str[-2:]=='\n\r'): return str[:-2]
  150.     if len(str)>1:
  151.         if str[-1]=='\n': return str[:-1]
  152.     return str
  153.  
  154. # broadcast: send a msg to all connections, except self.
  155. def broadcast(self_idx,msg):
  156.     i=0
  157.     for c in connections:
  158.         if i!=self_idx: c.sock.send(msg)
  159.         i=i+1
  160.  
  161. # shutdown: close connection & send msg to quitter & others
  162. def shutdown(i,msg,brmsg):
  163.     if brmsg: broadcast(i,brmsg)
  164.     connections[i].send(msg)
  165.     log(i,"QUIT")
  166.     del connections[i]
  167.  
  168. # log: log message from connection #i (to screen)
  169. def log(i,msg):
  170.     if connections[i].name():
  171.         print msg+'\t'+connections[i].name()+'\t'+`connections[i].address`,time.ctime(time.time())
  172.     else:
  173.         print msg+"\t<???>\t"+`connections[i].address`,time.ctime(time.time())
  174.  
  175. # mkerr: convert string into error string
  176. # mknote: convert string into note string
  177. def mkerr(msg):
  178.     return "** ERROR: "+msg+" **\r\n"
  179. def mknote(msg):
  180.     return "** NOTE: "+msg+" **\r\n"
  181.  
  182. # do_feeling: execute feeling 'data'. i=index of active socket.
  183. def do_feeling(i,data):
  184.     line=string.split(data)
  185.     feeling=string.lower(line[0])[1:]
  186.     if len(line)>1:
  187.         args=line[1:]
  188.     else:
  189.         args=None
  190.  
  191.     if feeling in feelings.keys():
  192.         adv=""
  193.         if feelings[feeling][1]:
  194.             adv=' '+feelings[feeling][1][0]
  195.         connections[i].send("You "+feeling+adv+".\r\n")
  196.         if feeling[-2:]=='ch' or feeling[-1]=='s' or feeling[-2:]=='sh':
  197.             feeling=feeling+'e'
  198.         if feeling[-1]=='y':
  199.             feeling=feeling[:-1]+"ie"
  200.         broadcast(i,connections[i].name()+' '+feeling+'s'+adv+".\r\n")
  201.     else:
  202.         connections[i].senderr("Unknown feeling")
  203.  
  204.  
  205. # do_chatter: broadcast normal chatter
  206. def do_chatter(i, data):
  207.     broadcast(i,connections[i].name()+" says: `"+data+"'\r\n")
  208.  
  209. # do_to: send chatter to only one person
  210. def do_to(i, args):
  211.     if args: 
  212.         destname=args[0]
  213.     else:
  214.         connections[i].senderr("No destination provided")
  215.         return
  216.     data=string.join(args[1:])
  217.     if destname in map(lambda x:x.name(),connections):
  218.         dest=map(lambda x:x.name(),connections).index(destname)
  219.         connections[dest].send(connections[i].name()+" says to you: `"+data+"'\r\n")
  220.     else:
  221.         connections[i].senderr("Unknown name")
  222.  
  223. # do_help: send a help message
  224. def do_help(i, args):
  225.     if args and args[0]=="feelings":
  226.         connections[i].send("** FEELINGS **\r\n"
  227.         "Currently, the following feeling commands have been defined:\r\n")
  228.         s=""
  229.         fls=feelings.keys()
  230.         fls.sort()
  231.         for f in fls:
  232.             if (len(s)+len(f))>=76:
  233.                 connections[i].send(s+"\r\n")
  234.                 s=""
  235.             s=s+f+' '
  236.         connections[i].send(s+"\n")
  237.     else:
  238.         connections[i].send("** CHAT SERVICE ONLINE HELP **\r\n"
  239.         "All normal text entered is sent to all other people, as if you 'said' it.\r\n"
  240.         "Lines starting with ' are a special feelings line. See @help feelings.\r\n"
  241.         "Lines starting with @ are regarded as a special command line:\r\n"
  242.         "@help topic - help on topic\r\n"
  243.         "@to foo bar - say 'bar' to 'foo' only\r\n"
  244.         "@who        - who's there\r\n"
  245.         "@name foo   - set your name to 'foo'\r\n"
  246.         "@quit       - exit chat service\r\n"
  247.         "@eject who passwd - eject someone (operator cmd)\r\n"
  248.         "@shutdown passwd) - shutdown server (operator cmd)\r\n")
  249.  
  250. # do_who: show who are there
  251. def do_who(i):
  252.     str=""
  253.     for c in connections:
  254.         str=str+"  "+c.name()+"\r\n"
  255.     connections[i].send(str)
  256.     
  257.  
  258. # do_name: change name
  259. def do_name(i,args):
  260.     if not args:
  261.         connections[i].senderr("No name")
  262.     elif len(args[0]) < 2:
  263.         connections[i].senderr("Name too short")
  264.     elif args[0] in map(lambda x:x.name(),connections):
  265.         connections[i].senderr("Name already in use")
  266.     else:
  267.         broadcast(-1,mknote(connections[i].name()+" now known as: "+args[0]))
  268.         connections[i].setname(args[0])
  269.  
  270. # do_eject: eject someone, need password for this
  271. def do_eject(i, args):
  272.     if not password:
  273.         connections[i].senderr("Sysop commands not enabled")
  274.     elif len(args) != 2:
  275.         connections[i].senderr("Usage: @eject name password")
  276.     elif not (args[0] in map(lambda x:x.name(), connections)):
  277.         connections[i].senderr("Unknown name")
  278.     elif args[1] != password:
  279.         connections[i].senderr("Wrong password")
  280.     else:
  281.         dest=map(lambda x:x.name(),connections).index(args[0])
  282.         shutdown(dest,"You were ejected by "+connections[i].name()+".\n",mknote(connections[dest].name()+" WAS EJECTED BY "+connections[i].name()))
  283.     
  284. # do_shutdown: shut down the server (needs passwd)
  285. def do_shutdown(i, args):
  286.     if not password:
  287.         connections[i].senderr("Sysop commands not enabled")
  288.     elif len(args) != 1:
  289.         connections[i].senderr("Usage: @shutdown password")
  290.     elif args[0] != password:
  291.         connections[i].senderr("Wrong password")
  292.     else:
  293.         broadcast(-1,mknote("Server shutdown by "+connections[i].name()))
  294.         log(i,"SHUTDOWN");
  295.         exit(0)
  296.  
  297. # do_command: execute command 'data'. i=index of active socket.
  298. def do_command(i,data):
  299.     line=string.split(data)
  300.     cmd=string.lower(line[0])[1:]
  301.     args=line[1:]
  302.  
  303.     if cmd=='name':
  304.         do_name(i,args)
  305.     elif cmd=="who":
  306.         do_who(i)
  307.     elif cmd=="quit":
  308.         shutdown(i,"Quitting chat service.\n",mknote(connections[i].name()+" LEFT"))
  309.     elif cmd=="to":
  310.         do_to(i, args)
  311.     elif cmd=="help":
  312.         do_help(i, args)
  313.     elif cmd=="eject":
  314.         do_eject(i, args)
  315.     elif cmd=="shutdown":
  316.         do_shutdown(i, args)
  317.     else:
  318.         connections[i].senderr("Bad CMD, try @help")
  319.  
  320.  
  321. def serverloop(host,port):
  322.     global feelings
  323.  
  324.     print "\nStarting telnet chat service on",host,port,"..."
  325.  
  326.     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  327.     s.bind(host, port)
  328.     s.listen(1)
  329.  
  330.     while 1:
  331.         socklist=map(lambda x:x.sock, connections)
  332.         ins,outs,exs = select.select(socklist+[s],[],[],5)
  333.  
  334.         for sock in ins:
  335.             if sock==s:
  336.                 sock,addr=s.accept()
  337.                 connections.append(connection(sock,addr))
  338.                 sock.send(WelcomeMsg+"Enter your name: ")
  339.  
  340.             else:
  341.                 i = socklist.index(sock)
  342.                 data=sock.recv(512)
  343.                 if data:
  344.                     data=string.strip(stripcrlf(data))
  345.  
  346.                     # If we don't have a name yet, read the name.
  347.                     if not connections[i].name():
  348.                         if len(data)<2:
  349.                             shutdown(i,mkerr("Name too short, try again"),None)
  350.                         elif data in map(lambda x:x.name(),connections):
  351.                             shutdown(i,mkerr("Name already in use, try again"),None)
  352.                         else:
  353.                             connections[i].setname(data)
  354.                             broadcast(i,mknote(connections[i].name()+" ARRIVES"))
  355.                             connections[i].sendnote("Ok, enter chatter or @help for help")
  356.                             log(i,"CONNECT")
  357.                             
  358.                     elif data:
  359.                         if data[0]=='\'':
  360.                             do_feeling(i,data)
  361.                         elif data[0]=='@':
  362.                             do_command(i,data)
  363.                         else:
  364.                             do_chatter(i,data)
  365.  
  366.                     if sock.fileno()>0: sock.send("> ")
  367.  
  368.                 else:
  369.                     # EOF read or closed connection.
  370.                     shutdown(i,"Quitting chat service.\r\n",mknote(connections[i].name()+" LEAVES"))
  371.  
  372.  
  373. def main(host,port):
  374.     global connections, password 
  375.  
  376.     password=raw_input("Enter password (or just enter for no passwd): ");
  377.  
  378.     serverloop(host,port)
  379.     print "Server shut down."
  380.     del connections            # close all connections
  381.  
  382.  
  383. if __name__=='__main__':
  384.  
  385.     argc=len(sys.argv)
  386.     if argc==1:
  387.         main(socket.gethostname(),9999)
  388.     elif argc==2:
  389.         main(socket.gethostname(),eval(sys.argv[1]))
  390.     elif argc==3:
  391.         main(sys.argv[1],eval(sys.argv[2]))
  392.     else:
  393.         print "Usage: python",sys.argv[0],"[hostname] [portnumber]"
  394.